package core

import (
	"errors"
	"fmt"
	"gitee.com/hongzhaomin/gspring/iface"
	"gitee.com/hongzhaomin/gspring/tools"
	"os"
	"reflect"
)

// ================================================================
// ======================= InjectionMetadata ======================
// ================================================================

type InjectionMetadata struct {
	targetBean       any
	injectedElements []InjectedElement
}

func NewInjectionMetadata(targetBean any, elements []InjectedElement) *InjectionMetadata {
	return &InjectionMetadata{
		targetBean:       targetBean,
		injectedElements: elements,
	}
}

func (my *InjectionMetadata) Inject(targetBeanName string, factory iface.BeanFactory) {
	for _, element := range my.injectedElements {
		element.EleInject(targetBeanName, factory)
	}
}

// ==============================================================
// ======================= InjectedElement ======================
// ==============================================================

type InjectedElement interface {
	EleInject(targetBeanName string, factory iface.BeanFactory)
}

// ======================================================================
// ======================= AbstractInjectedElement ======================
// ======================================================================

type AbstractInjectedElement struct {

	// 需要注入属性对象的名称
	name string

	// 属性是否为结构体指针
	isPtr bool

	// 属性的反射Type：如果是结构体指针，则表示指针指向的结构体类型
	rtField reflect.Type

	// 属性的反射Value：表示原始的reflect.Value
	rvField reflect.Value
}

func NewAbstractElement(name string, isPtr bool, rtField reflect.Type, rvField reflect.Value) *AbstractInjectedElement {
	return &AbstractInjectedElement{
		isPtr:   isPtr,
		rtField: rtField,
		rvField: rvField,
		name:    name,
	}
}

func (my *AbstractInjectedElement) EleInject(targetBeanName string, factory iface.BeanFactory) {
	//TODO implement me
	panic("AbstractInjectedElement: I am a abstract class, please implement me.")
}

func (my *AbstractInjectedElement) getObjectToInject(factory iface.BeanFactory) any {
	bean, err := my.getObjectToInjectNoPanic(factory)
	if err != nil {
		panic(err)
	}
	return bean
}

func (my *AbstractInjectedElement) getObjectToInjectNoPanic(factory iface.BeanFactory) (bean any, err error) {
	defer func() {
		recoverErr := recover()
		if recoverErr != nil {
			switch errMsg := recoverErr.(type) {
			case string:
				err = errors.New(errMsg)
			case error:
				err = errMsg
			default:
				err = errors.New("未知错误")
			}
		}
	}()

	if my.rtField.Kind() != reflect.Struct && my.rtField.Kind() != reflect.Interface {
		panic(errors.New("注入类型不是结构体也不是接口"))
	}

	// 优先根据类型注入
	fullPathName := tools.GetFullPathNameByRt(my.rtField)
	beanNames := factory.(iface.ListableBeanFactory).GetBeanNamesForType(my.rtField)
	if len(beanNames) <= 0 {
		panic(errors.New(fmt.Sprintf("bean not found: '%s'", fullPathName)))
	} else if len(beanNames) == 1 {
		bean = factory.GetBeanByName(beanNames[0])
		return
	} else {
		if !tools.Contains(beanNames, my.name) {
			panic(errors.New(fmt.Sprintf("required a single bean, but 2 were found: '%s'", fullPathName)))
		}
		bean = factory.GetBeanByName(my.name)
		return
	}
}

// ==============================================================
// ======================= ResourceElement ======================
// ==============================================================
var _ InjectedElement = (*ResourceElement)(nil)

func NewResourceElement(fieldName string, isPtr bool, rtField reflect.Type, rvField reflect.Value) InjectedElement {
	return &ResourceElement{
		AbstractInjectedElement: *NewAbstractElement(fieldName, isPtr, rtField, rvField),
	}
}

type ResourceElement struct {
	AbstractInjectedElement
}

func (my *ResourceElement) EleInject(targetBeanName string, factory iface.BeanFactory) {
	defer func() {
		recoverErr := recover()
		if recoverErr != nil {
			switch errMsg := recoverErr.(type) {
			case string:
				panic("@Resource 依赖注入失败：" + errMsg)
			case error:
				panic(errors.New("@Resource 依赖注入失败：" + errMsg.Error()))
			default:
				fmt.Println(errMsg)
				panic(errors.New("@Resource 依赖注入失败"))
			}
		}
	}()

	beanName := my.name
	rtField := my.rtField
	switch rtField.Kind() {
	case reflect.Interface, reflect.Struct:
		if !my.isPtr && rtField.Kind() != reflect.Interface {
			// 属性类型不是指针并且不是接口类型，是不支持依赖注入的
			msg := fmt.Sprintf("The attribute type named [%s] is not supported for assignment", beanName)
			panic(errors.New(msg))
		}
		my.rvField.Set(reflect.ValueOf(my.getObjectToInject(factory)))
	case reflect.Slice:
		// 字段为切片类型，直接根据元素类型获取bean的集合
		rtFieldEle := rtField.Elem() // 切片元素类型
		if rtFieldEle.Kind() != reflect.Interface &&
			// 不支持结构体类型，因为如果此时赋值了半成品对象，那这个对象即使后面被赋值成成品对象，原半成品对象也不会改变
			!(rtFieldEle.Kind() == reflect.Ptr && rtFieldEle.Elem().Kind() == reflect.Struct) {
			msg := fmt.Sprintf("The slice type attribute type named [%s] is not supported for assignment", beanName)
			panic(errors.New(msg))
		}

		rvGetBeans := reflect.MakeSlice(rtField, 0, 2)
		for _, bean := range factory.GetBeans(rtFieldEle) {
			rvGetBeans = reflect.Append(rvGetBeans, reflect.ValueOf(bean))
		}

		if my.isPtr { // 切片指针
			// 先创建一个指针
			rvPtrGetBeans := reflect.New(rtField)
			// 再将切片值赋值给该新建的切片指针
			rvPtrGetBeans.Elem().Set(rvGetBeans)
			// 最后将该切片指针赋值到该字段上
			my.rvField.Set(rvPtrGetBeans)
		} else {
			// 直接将切片值赋值到该字段上
			my.rvField.Set(rvGetBeans)
		}
	default:
		msg := fmt.Sprintf("The attribute type named [%s] is not supported for assignment", beanName)
		panic(errors.New(msg))
	}
}

// ==============================================================
// ======================= ExtendsElement =======================
// ==============================================================
var _ InjectedElement = (*ExtendsElement)(nil)

func NewExtendsElement(fieldName string, isPtr bool, rtField reflect.Type, rvField reflect.Value) InjectedElement {
	return &ExtendsElement{
		AbstractInjectedElement: *NewAbstractElement(fieldName, isPtr, rtField, rvField),
	}
}

type ExtendsElement struct {
	AbstractInjectedElement
}

func (my *ExtendsElement) EleInject(targetBeanName string, factory iface.BeanFactory) {
	rtField := my.rtField
	// 如果是结构体指针，则取容器的Bean赋值
	if rtField.Kind() == reflect.Interface || (rtField.Kind() == reflect.Struct && my.isPtr) {
		getBean := my.getObjectToInject(factory)
		my.rvField.Set(reflect.ValueOf(getBean))
		//} else if rtField.Kind() == reflect.Interface {
		//	// 如果是接口，则要创建一个新对象，然后取容器的Bean赋值到这个新对象上
		//	getBean := my.getObjectToInject(factory)
		//	my.rvField.Set(reflect.ValueOf(getBean))
	}
}

// ==============================================================
// ======================== ValueElement ========================
// ==============================================================
var _ InjectedElement = (*ValueElement)(nil)

func NewValueElement(expr, key, defaultVal string, isPtr bool, rtField reflect.Type, rvField reflect.Value) InjectedElement {
	return &ValueElement{
		AbstractInjectedElement: *NewAbstractElement("", isPtr, rtField, rvField),
		expr:                    expr,
		key:                     key,
		defaultVal:              defaultVal,
	}
}

type ValueElement struct {
	AbstractInjectedElement

	// @Value的值，为一个表达式
	// For example: ${server.port:8080}, 表示如果server.port无配置则取默认值8080
	expr string

	// 从表达式中解析出来的key，表示需要从环境配置中取值的key
	// For example: 上例中的 server.port
	// 一般属性的类型是基本类型，例如：string/bool/int/float
	key string

	// 从表达式中解析出来的默认值，表示如果从环境配置中取不到值，就用默认值
	// For example: 上例中的 8080
	defaultVal string
}

func (my *ValueElement) EleInject(targetBeanName string, factory iface.BeanFactory) {
	rtField := my.rtField
	ptrField := my.isPtr
	// 从环境配置中取值
	env := factory.GetBeanByType((*iface.Environment)(nil)).(iface.Environment)
	valStr := my.getValFromEnv(env, my.key)
	if valStr == "" {
		valStr = my.defaultVal
		if valStr == "" {
			panic(fmt.Errorf("express [%s] inject fail: properties [%s] no config", my.expr, my.key))
		}
	}

	// 反射赋值
	rvPtr, err := convRv4Str(rtField, valStr)
	if err != nil {
		panic(fmt.Errorf("express [%s] inject fail: %s", my.expr, err.Error()))
	}
	if ptrField {
		my.rvField.Set(rvPtr)
	} else {
		my.rvField.Set(rvPtr.Elem())
	}
}

func (my *ValueElement) getValFromEnv(env iface.Environment, key string) string {
	valStr := env.GetString(key)
	return os.Expand(valStr, func(embeddedKey string) string {
		return my.getValFromEnv(env, embeddedKey)
	})
}
